#include "Meshes.h"
#include "MeshInstances.h"

#define NEAR_PLANE_VAL 0.5f
#define FAR_PLANE_VAL 1.0f
#define FAR_PLANE_DIST 3.0f
#define NEAR_PLANE_DIST (FAR_PLANE_DIST)

#define v3f Vector3f
#define VERTEX_INIT(shape, i) Vertex(frustrumPos[i], frustrumTX[i])
static Vector3f frustrumPos[] = {
	v3f(0,0,0),	// head of the frustrum
	// near plane
	v3f(-NEAR_PLANE_VAL, -NEAR_PLANE_VAL, NEAR_PLANE_VAL*NEAR_PLANE_DIST), v3f(-NEAR_PLANE_VAL, NEAR_PLANE_VAL,  NEAR_PLANE_VAL*NEAR_PLANE_DIST),  // left wall
	v3f(NEAR_PLANE_VAL, NEAR_PLANE_VAL,  NEAR_PLANE_VAL*NEAR_PLANE_DIST), v3f(NEAR_PLANE_VAL, -NEAR_PLANE_VAL,  NEAR_PLANE_VAL*NEAR_PLANE_DIST), // right wall
	//far plane
	v3f(-FAR_PLANE_VAL, -FAR_PLANE_VAL, FAR_PLANE_VAL*FAR_PLANE_DIST), v3f(-FAR_PLANE_VAL, FAR_PLANE_VAL, FAR_PLANE_VAL*FAR_PLANE_DIST),  // left wall
	v3f(FAR_PLANE_VAL, FAR_PLANE_VAL, FAR_PLANE_VAL*FAR_PLANE_DIST), v3f(FAR_PLANE_VAL, -FAR_PLANE_VAL, FAR_PLANE_VAL*FAR_PLANE_DIST),  // right wall
};

static Vector3f frustrumTX[] = {
	v3f(0,0,0),	// head of the frustrum
	// near plane
	v3f(0, 0, 0), v3f(0, 0, 0),  // left wall
	v3f(0, 0, 0), v3f(0, 0, 0), // right wall
	//far plane
	v3f(0,1,1), v3f(0,0,1),	//left wall
	v3f(1,0,1), v3f(1,1,1)  // right wall
};

static float frustrumStreched[] = {
	0,
	0, 0,
	0, 0,
	1, 1, 
	1, 1
};

static UINT frustIndices[] = {
	0, 6, 5, // left wall
	0, 7, 8, // right wall
	0, 7, 6, // top wall
	0, 8, 5, // bottom wall
	// near plane
	1, 3, 2,
	1, 4, 3,
	// far plane
	5, 7, 6,
	5, 8, 7
};

static Vector3f QuadPos[] = {
	v3f(-1, 1, 0), v3f(1, 1, 0),
	v3f(-1,-1, 0), v3f(1, -1, 0) 
};

static UINT QuadIndices[] = {
	0, 1, 2,
	2, 3, 1
};

static Vector3f QuadTX[] = {
	v3f(0,0,0), v3f(1,0,0),
	v3f(0,1,0), v3f(1,1,0)
};

static Vertex Quad[] = {
	VERTEX_INIT(Quad, 0), VERTEX_INIT(Quad, 1),
	VERTEX_INIT(Quad, 2), VERTEX_INIT(Quad, 3)
};

static Vector3f Triangle[] = {
	v3f(0, 2, 0), v3f(1, -1, 0), v3f(0, -1, -1)
};

CCameraMesh::CCameraMesh() : m_pIndices(frustIndices), m_NumVertices(sizeof(frustrumPos)/sizeof(Vector3f)), m_NumIndices(sizeof(frustIndices)/sizeof(UINT))
{
	// creating the vertex buffer of the mesh
	
	InitVertices();
	m_VBO = new GLuint;
	glGenBuffers(1, m_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, *m_VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*m_NumVertices, m_pVertices, GL_STATIC_DRAW);
	m_IBO = new GLuint;
	glGenBuffers(1, m_IBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *m_IBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(UINT)*m_NumIndices, m_pIndices, GL_STATIC_DRAW);
	//assuming all went ok
	
	CGLProgram* pProgram = new CGLProgram("..\\shaders\\camera.vs", "..\\shaders\\camera.frag");
	m_GLMethod.pProgram = pProgram;
	
	m_GLMethod.MapShaderVar<VS, UNIFORM>("WVPMat");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("Position");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("iTexCoord");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("bStreched");
	m_GLMethod.MapShaderVar<VS, UNIFORM>("strechFactor");
	m_GLMethod.MapShaderVar<FS, UNIFORM>("gSampler");
	m_GLMethod.MapShaderVar<FS, UNIFORM>("bSample");
	m_GLMethod.MapShaderVar<FS, UNIFORM>("col");

	CGLProgram* pPickProgram = new CGLProgram("..\\shaders\\cameraPicking.vs", "..\\shaders\\cameraPicking.frag");
	m_GLPicking.pProgram = pPickProgram;

	m_GLPicking.MapShaderVar<VS, UNIFORM>("WVPMat");
	m_GLPicking.MapShaderVar<VS, ATTRIB>("Position");
	m_GLPicking.MapShaderVar<VS, ATTRIB>("bStreched");
	m_GLPicking.MapShaderVar<VS, UNIFORM>("strechFactor");
	m_GLPicking.MapShaderVar<FS, UNIFORM>("gIndex");


}

//this method will init the vertices to hold the vertex data
void CCameraMesh::InitVertices()
{
	m_pVertices = new Vertex[m_NumVertices];
	for (UINT i = 0; i < m_NumVertices; i++)
	{
		m_pVertices[i] = VERTEX_INIT(frustrom, i);
		m_pVertices[i].bStreched = frustrumStreched[i];
	}
}

CCameraMesh::~CCameraMesh()
{
	delete m_VBO;
	delete m_IBO;

	delete m_GLPicking.pProgram;
	delete m_GLMethod.pProgram;
}

CCameraMeshInstance* CCameraMesh::CreateInstance( Matrix4f Rotate, Matrix4f Translate, cv::Mat Image, float f )
{
	static UINT Index = 0;
	CCameraMeshInstance* newInstance = new CCameraMeshInstance(Rotate, Translate, this, Image, Index++, f);
	return newInstance;
}

SBoundingBox CCameraMesh::GetBoundingBox() const
{
	SBoundingBox Ret;
	Ret.Min = Vector3f(-FAR_PLANE_VAL, -FAR_PLANE_VAL, 0);
	Ret.Max = Vector3f(FAR_PLANE_VAL, FAR_PLANE_VAL, FAR_PLANE_VAL * FAR_PLANE_DIST);
	return Ret;
}

template<ShaderType shader, GLVarType var>
void SGLMethod::MapShaderVar( char* varName )
{
	GLuint loc;
	ShaderVars& varMap = (shader == VS) ? vsVars : psVars;
	switch (var)
	{
	case UNIFORM:
		loc = glGetUniformLocation(pProgram->GetProgramObject(), varName);
		break;
	case ATTRIB:
		loc = glGetAttribLocation(pProgram->GetProgramObject(), varName);
		break;
	}
	VISIO_ASSERT((loc != 0xFFFFFFFF));
	varMap[varName] = loc;
}

CQuadMesh::CQuadMesh() : m_pVertices(Quad), m_pIndices(QuadIndices), m_Texture(GL_TEXTURE_2D, "beaver.png"),
						 m_NumVertices(sizeof(Quad)/sizeof(Vertex)), m_NumIndices(sizeof(QuadIndices)/sizeof(UINT))

{
	m_VBO = new GLuint;
	glGenBuffers(1, m_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, *m_VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*m_NumVertices, m_pVertices, GL_STATIC_DRAW);
	glGenBuffers(1, &m_IBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(UINT)*m_NumIndices, m_pIndices, GL_STATIC_DRAW);
	//assuming all went ok

	CGLProgram* pProgram = new CGLProgram("..\\shaders\\camera.vs", "..\\shaders\\camera.frag");
	m_GLMethod.pProgram = pProgram;

	m_GLMethod.MapShaderVar<VS, UNIFORM>("WVPMat");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("Position");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("iTexCoord");
	m_GLMethod.MapShaderVar<FS, UNIFORM>("gSampler");
	VISIO_ASSERT(m_Texture.Load());
}

CQuadMeshInstance* CQuadMesh::CreateInstance()
{
	CQuadMeshInstance* pNewInst = new CQuadMeshInstance(this);
	return pNewInst;
}

CPointCloudMesh::CPointCloudMesh( double* points, float* colors, UINT nVertices ) : m_NumVertices(nVertices) 
{
	m_pVertices = new Vertex[m_NumVertices];

	for (int i = 0; i < m_NumVertices; i++)
	{
		m_pVertices[i].position = Vector3f(points[3*i], points[3*i + 1], -points[3*i + 2]);
		m_pVertices[i].texCoord = Vector3f(colors[3*i], colors[3*i + 1],  colors[3*i + 2]);
	}

	m_VBO = new GLuint;
	glGenBuffers(1, m_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, *m_VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*m_NumVertices, m_pVertices, GL_STATIC_DRAW);

	CGLProgram* pProgram = new CGLProgram("..\\shaders\\PointCloud.vs", "..\\shaders\\PointCloud.frag");
	m_GLMethod.pProgram = pProgram;

	m_GLMethod.MapShaderVar<VS, UNIFORM>("WVPMat");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("Position");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("color");
}

CPointCloudMeshInstance* CPointCloudMesh::CreateInstance()
{
	return new CPointCloudMeshInstance(this);
}

SBoundingBox CPointCloudMesh::GetBoundingBox() const
{
	Vector3f max(FLT_MIN, FLT_MIN, FLT_MIN);
	Vector3f min(FLT_MAX, FLT_MAX, FLT_MAX);
	SBoundingBox Ret;
	for (UINT i = 0; i < m_NumVertices; i++)
	{
		min = Vector3f::Min(m_pVertices[i].position, min);
		max = Vector3f::Max(m_pVertices[i].position, max);
	}

	Ret.Min = min;
	Ret.Max = max;

	return Ret;
}

CPointOfInterest::CPointOfInterest(Vector3f FocusPoint)
{
	m_FocusPoint = FocusPoint;
	m_pVertex = new Vertex[3];

	for (int i = 0; i < 3; i++)
	{
		m_pVertex[i] = Vertex(Triangle[i], Vector3f(0.0f, 1.0f, 0.0f));
	}

	m_VBO = new GLuint;
	glGenBuffers(1, m_VBO);
	glBindBuffer(GL_ARRAY_BUFFER, *m_VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 3, m_pVertex, GL_STATIC_DRAW);
	
	CGLProgram* pProgram = new CGLProgram("..\\shaders\\PointCloud.vs", "..\\shaders\\PointCloud.frag");
	m_GLMethod.pProgram = pProgram;

	m_GLMethod.MapShaderVar<VS, UNIFORM>("WVPMat");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("Position");
	m_GLMethod.MapShaderVar<VS, ATTRIB>("color");
}

void CPointOfInterest::Draw( Matrix4f WVP, Vector3f FocusPoint )
{
	const float scaleFactor = 0.1f;
	m_FocusPoint = FocusPoint;
	Matrix4f Scale, Translate;
	Scale.InitScaleTransform(scaleFactor, scaleFactor, scaleFactor);
	Translate.InitTranslationTransform(FocusPoint.x, FocusPoint.y, FocusPoint.z);
	glBindBuffer(GL_ARRAY_BUFFER, m_VBO[0]);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	WVP = WVP * Translate * Scale;
	GLuint progObject =m_GLMethod.pProgram->GetProgramObject();
	glUseProgram(progObject);
	GLuint WVPLoc = m_GLMethod.vsVars["WVPMat"];
	GLuint PosLoc = m_GLMethod.vsVars["Position"];
	GLuint ColLoc = m_GLMethod.vsVars["color"];

	glUniformMatrix4fv(WVPLoc, 1, GL_TRUE, &WVP.m[0][0]);
	glEnableVertexAttribArray(PosLoc);
	glVertexAttribPointer(PosLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
	glVertexAttribPointer(ColLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)12);
	glDrawArrays(GL_TRIANGLES, 0, 3);

	glDisableVertexAttribArray(PosLoc);
}
